﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using Object = UnityEngine.Object;

namespace Framework
{
    /// <summary>
    /// 场景管理器
    /// </summary>
    public class SceneManager : MonoBehaviour
    {
        #region Define

        /// <summary>
        /// 场景状态接口
        /// </summary>
        public interface ISceneState
        {
            /// <summary>
            /// 加载状态时的回调
            /// </summary>
            void OnLoad();
            
            /// <summary>
            /// 进入状态的回调
            /// </summary>
            void OnEnter(RectTransform sceneRoot);

            /// <summary>
            /// 离开状态的回调
            /// </summary>
            void OnExit(RectTransform sceneRoot);
        }

        /// <summary>
        /// 场景视图接口
        /// </summary>
        public interface ISceneView
        {
            
        }
        
        #endregion
        
        /// <summary>
        /// 场景对象的挂载位点
        /// </summary>
        [SerializeField] 
        private RectTransform _SceneRoot;

        /// <summary>
        /// 初始场景名称
        /// </summary>
        [SerializeField]
        private string _BootSceneName;

        /// <summary>
        /// 场景视图加载路径
        /// </summary>
        public string SceneViewLoadPath;
        
        /// <summary>
        /// 当前场景状态
        /// </summary>
        private ISceneState _CurrentSceneState;
        
        /// <summary>
        /// 已经入栈的场景状态
        /// </summary>
        private Stack<ISceneState> _StackedSceneState;

        private void Awake()
        {
            _StackedSceneState = new Stack<ISceneState>();
        }

        private void Start()
        {
            Instance = this;
            StateChange(_BootSceneName);
        }

        /// <summary>
        /// 根据名称获取场景实例
        /// </summary>
        private ISceneState _GetISceneStateByName(string sceneStateName)
        {
            var targetType = typeof(ISceneState);
            foreach (var type in Assembly.GetExecutingAssembly().GetTypes().Where( type => targetType.IsAssignableFrom(type) ))
            {
                if (type.Name == sceneStateName)
                    return (ISceneState) Activator.CreateInstance(type);
            }
            return null;
        }

        #region API

        /// <summary>
        /// 全局单例
        /// </summary>
        public static SceneManager Instance { get; protected set; }

        /// <summary>
        /// 切换场景状态
        /// </summary>
        public void StateChange(ISceneState targetState)
        {
            _CurrentSceneState?.OnExit(_SceneRoot);
            _CurrentSceneState = targetState;
            _CurrentSceneState?.OnLoad();
            _CurrentSceneState?.OnEnter(_SceneRoot);
        }

        /// <summary>
        /// 切换场景状态
        /// </summary>
        public void StateChange(string sceneStateName)
        {
            StateChange(_GetISceneStateByName(sceneStateName));
        }

        /// <summary>
        /// 入栈当前场景状态并切换场景状态
        /// </summary>
        public void StatePush(ISceneState targetState)
        {
            _StackedSceneState.Push(_CurrentSceneState);
            StateChange(targetState);
        }

        /// <summary>
        /// 入栈当前场景状态并切换场景状态
        /// </summary>
        public void StatePush(string sceneStateName)
        {
            StatePush(_GetISceneStateByName(sceneStateName));
        }

        /// <summary>
        /// 切换到从栈中弹出的场景状态
        /// </summary>
        public void StatePop()
        {
            StateChange(_StackedSceneState.Pop());
        }

        #endregion
    }
    
    /// <summary>
    /// 场景状态扩展
    /// </summary>
    public static class SceneStateExtension
    {
        /// <summary>
        /// 加载场景视图
        /// </summary>
        public static T LoadView<T>(this SceneManager.ISceneState sceneState) where T : class, SceneManager.ISceneView
        {
            var sceneManager = SceneManager.Instance;
            var type = typeof(T);
            var prefab = Resources.Load<GameObject>($"{sceneManager.SceneViewLoadPath}/{type.Name}");
            return prefab != null ? (Object.Instantiate<GameObject>(prefab).GetComponent<T>()) : null;
        }
    }
}
